/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.api.ssh.server;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

import org.apache.commons.fileupload.FileItem;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.annotations.GenerateLink;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.util.LinksHelper;
import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl;
import org.eclipse.che.api.ssh.shared.Constants;
import org.eclipse.che.api.ssh.shared.dto.GenerateSshPairRequest;
import org.eclipse.che.api.ssh.shared.dto.SshPairDto;
import org.eclipse.che.api.ssh.shared.model.SshPair;
import org.eclipse.che.commons.env.EnvironmentContext;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.che.api.ssh.shared.Constants.LINK_REL_GET_PAIR;
import static org.eclipse.che.api.ssh.shared.Constants.LINK_REL_REMOVE_PAIR;
import static org.eclipse.che.dto.server.DtoFactory.newDto;

/**
 * Defines Ssh Rest API.
 *
 * @author Sergii Leschenko
 */
@Api(value = "/ssh", description = "Ssh REST API")
@Path("/ssh")
public class SshService extends Service {
    private final SshManager sshManager;

    @Inject
    public SshService(SshManager sshManager) {
        this.sshManager = sshManager;
    }

    @POST
    @Path("generate")
    @Consumes(APPLICATION_JSON)
    @Produces(APPLICATION_JSON)
    @GenerateLink(rel = Constants.LINK_REL_GENERATE_PAIR)
    @ApiOperation(value = "Generate and stores ssh pair based on the request",
                  notes = "This operation can be performed only by authorized user," +
                          "this user will be the owner of the created ssh pair",
                  response = SshPairDto.class)
    @ApiResponses({@ApiResponse(code = 201, message = "The ssh pair successfully generated"),
                   @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
                   @ApiResponse(code = 409, message = "Conflict error occurred during the ssh pair generation" +
                                                      "(e.g. The Ssh pair with such name and service already exists)"),
                   @ApiResponse(code = 500, message = "Internal server error occurred")})
    public Response generatePair(@ApiParam(value = "The configuration to generate the new ssh pair", required = true)
                                   GenerateSshPairRequest request) throws BadRequestException, ServerException, ConflictException {
        requiredNotNull(request, "Generate ssh pair request required");
        requiredNotNull(request.getService(), "Service name required");
        requiredNotNull(request.getName(), "Name required");
        final SshPairImpl generatedPair = sshManager.generatePair(getCurrentUserId(), request.getService(), request.getName());

        return Response.status(Response.Status.CREATED)
                       .entity(asDto(injectLinks(asDto(generatedPair))))
                       .build();
    }

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_HTML)
    @GenerateLink(rel = Constants.LINK_REL_CREATE_PAIR)
    public Response createPair(Iterator<FileItem> formData) throws BadRequestException, ServerException, ConflictException {
        String service = null;
        String name = null;
        String privateKey = null;
        String publicKey = null;

        while (formData.hasNext()) {
            FileItem item = formData.next();
            String fieldName = item.getFieldName();
            switch (fieldName) {
                case "service":
                    service = item.getString();
                    break;
                case "name":
                    name = item.getString();
                    break;
                case "privateKey":
                    privateKey = item.getString();
                    break;
                case "publicKey":
                    publicKey = item.getString();
                    break;
                default:
                    //do nothing
            }
        }

        requiredNotNull(service, "Service name required");
        requiredNotNull(name, "Name required");
        if (privateKey == null && publicKey == null) {
            throw new BadRequestException("Key content was not provided.");
        }

        sshManager.createPair(new SshPairImpl(getCurrentUserId(), service, name, publicKey, privateKey));

        // We should send 200 response code and body with empty line
        // through specific of html form that doesn't invoke complete submit handler
        return Response.ok("", MediaType.TEXT_HTML).build();
    }

    @POST
    @Consumes(APPLICATION_JSON)
    @GenerateLink(rel = Constants.LINK_REL_CREATE_PAIR)
    @ApiOperation(value = "Create a new ssh pair",
                  notes = "This operation can be performed only by authorized user," +
                          "this user will be the owner of the created ssh pair")
    @ApiResponses({@ApiResponse(code = 204, message = "The ssh pair successfully created"),
                   @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
                   @ApiResponse(code = 409, message = "Conflict error occurred during the ssh pair creation" +
                                                      "(e.g. The Ssh pair with such name and service already exists)"),
                   @ApiResponse(code = 500, message = "Internal server error occurred")})
    public void createPair(@ApiParam(value = "The ssh pair to create", required = true)
                           SshPairDto sshPair) throws BadRequestException, ServerException, ConflictException {
        requiredNotNull(sshPair, "Ssh pair required");
        requiredNotNull(sshPair.getService(), "Service name required");
        requiredNotNull(sshPair.getName(), "Name required");
        if (sshPair.getPublicKey() == null && sshPair.getPrivateKey() == null) {
            throw new BadRequestException("Key content was not provided.");
        }

        sshManager.createPair(new SshPairImpl(getCurrentUserId(), sshPair));
    }

    @GET
    @Path("{service}/find")
    @Produces(APPLICATION_JSON)
    @ApiOperation(value = "Get the ssh pair by the name of pair and name of service owned by the current user",
                  notes = "This operation can be performed only by authorized user.")
    @ApiResponses({@ApiResponse(code = 200, message = "The ssh pair successfully fetched"),
                   @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
                   @ApiResponse(code = 404, message = "The ssh pair with specified name and service does not exist for current user"),
                   @ApiResponse(code = 500, message = "Internal server error occurred")})
    public SshPairDto getPair(@ApiParam("Name of service")
                              @PathParam("service")
                              String service,
                              @ApiParam(value = "Name of ssh pair", required = true)
                              @QueryParam("name")
                              String name) throws NotFoundException, ServerException, BadRequestException {
        requiredNotNull(name, "Name of ssh pair");
        return injectLinks(asDto(sshManager.getPair(getCurrentUserId(), service, name)));
    }

    @DELETE
    @Path("{service}")
    @ApiOperation(value = "Remove the ssh pair by the name of pair and name of service owned by the current user")
    @ApiResponses({@ApiResponse(code = 204, message = "The ssh pair successfully removed"),
                   @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
                   @ApiResponse(code = 404, message = "The ssh pair doesn't exist"),
                   @ApiResponse(code = 500, message = "Internal server error occurred")})
    public void removePair(@ApiParam("Name of service")
                           @PathParam("service")
                           String service,
                           @ApiParam(value = "Name of ssh pair", required = true)
                           @QueryParam("name")
                           String name) throws ServerException, NotFoundException, BadRequestException {
        requiredNotNull(name, "Name of ssh pair");
        sshManager.removePair(getCurrentUserId(), service, name);
    }

    @GET
    @Path("{service}")
    @Produces(APPLICATION_JSON)
    @ApiOperation(value = "Get the ssh pairs by name of service owned by the current user",
                  notes = "This operation can be performed only by authorized user.",
                  response = SshPairDto.class,
                  responseContainer = "List")
    @ApiResponses({@ApiResponse(code = 200, message = "The ssh pairs successfully fetched"),
                   @ApiResponse(code = 500, message = "Internal server error occurred")})
    public List<SshPairDto> getPairs(@ApiParam("Name of service")
                                     @PathParam("service")
                                     String service) throws ServerException {
        return sshManager.getPairs(getCurrentUserId(), service)
                         .stream()
                         .map(sshPair -> injectLinks(asDto(sshPair)))
                         .collect(Collectors.toList());
    }

    private static String getCurrentUserId() {
        return EnvironmentContext.getCurrent().getSubject().getUserId();
    }

    private static SshPairDto asDto(SshPair pair) {
        return newDto(SshPairDto.class).withService(pair.getService())
                                       .withName(pair.getName())
                                       .withPublicKey(pair.getPublicKey())
                                       .withPrivateKey(pair.getPrivateKey());
    }

    private SshPairDto injectLinks(SshPairDto sshPairDto) {
        final UriBuilder uriBuilder = getServiceContext().getServiceUriBuilder();
        final Link getPairsLink = LinksHelper.createLink("GET",
                                                         uriBuilder.clone()
                                                                   .path(getClass(), "getPairs")
                                                                   .build(sshPairDto.getService())
                                                                   .toString(),
                                                         APPLICATION_JSON,
                                                         LINK_REL_GET_PAIR);

        final Link removePairLink = LinksHelper.createLink("DELETE",
                                                           uriBuilder.clone()
                                                                     .path(getClass(), "removePair")
                                                                     .build(sshPairDto.getService(), sshPairDto.getName())
                                                                     .toString(),
                                                           APPLICATION_JSON,
                                                           LINK_REL_REMOVE_PAIR);

        final Link getPairLink = LinksHelper.createLink("GET",
                                                        uriBuilder.clone()
                                                                  .path(getClass(), "getPair")
                                                                  .build(sshPairDto.getService(), sshPairDto.getName())
                                                                  .toString(),
                                                        APPLICATION_JSON,
                                                        LINK_REL_GET_PAIR);

        return sshPairDto.withLinks(Arrays.asList(getPairsLink, removePairLink, getPairLink));
    }

    /**
     * Checks object reference is not {@code null}
     *
     * @param object
     *         object reference to check
     * @param subject
     *         used as subject of exception message "{subject} required"
     * @throws BadRequestException
     *         when object reference is {@code null}
     */
    private void requiredNotNull(Object object, String subject) throws BadRequestException {
        if (object == null) {
            throw new BadRequestException(subject + " required");
        }
    }
}
